﻿using System;
using System.Threading;

namespace Encloser.Toolkit
{
    public class Atomic<T>
    {
        protected readonly ReaderWriterLock locker = new ReaderWriterLock();

        public T Value 
        { 
            get 
            {
                do { locker.AcquireReaderLock(999); } while (!locker.IsReaderLockHeld);
                var val = data;
                locker.ReleaseReaderLock();
                return val;
            }
            set
            {
                do { locker.AcquireWriterLock(999); } while (!locker.IsWriterLockHeld);
                data = value;
                locker.ReleaseWriterLock();
            } 
        }

        protected T data;

        public Atomic()
        {
            data = default;
        }

        public Atomic(T value)
        {
            data = value;
        }

        public void Get(Action<T> action)
        {
            if (action == null) return;
            do { locker.AcquireReaderLock(999); } while (!locker.IsReaderLockHeld);
            action(data);
            locker.ReleaseReaderLock();
        }

        public void Set(Action<T> action)
        {
            if (action == null) return;
            do { locker.AcquireWriterLock(999); } while (!locker.IsWriterLockHeld);
            action(data);
            locker.ReleaseWriterLock();
        }

        public void Set(Func<T, T> func)
        {
            if (func == null) return;
            do { locker.AcquireWriterLock(999); } while (!locker.IsWriterLockHeld);
            data = func(data);
            locker.ReleaseWriterLock();
        }

        public bool CAS(T oldValue, T newValue)
        {
            var f = true;
            Set(val =>
            {
                if (Equals(val, oldValue)) return newValue;
                f = false;
                return val;
            });
            return f;
        }
    }
}
